/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年10月24日
 */
package com.massyframework.beanskin.scriptengine.beancontext.provider;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.framework.Framework;

/**
 * 目录监视器
 * @author huangkh
 *
 */
final class ScriptFileWatcher implements Runnable {
	
	private Logger logger = LoggerFactory.getLogger(ScriptFileWatcher.class);
	
	private Framework framework;
	private Map<Path, Integer> pathMap;
	private WatchService watcher;

	/**
	 * 脚本目录监视器
	 */
	public ScriptFileWatcher(Framework framework) {
		this.framework = Objects.requireNonNull(framework, "\"framework\" cannot be null.");
		this.pathMap = new ConcurrentHashMap<>();
		try {
	        this.watcher = FileSystems.getDefault().newWatchService();
	    } catch (Exception e) {
	       if (logger.isErrorEnabled()) {
	    	   logger.error(e.getMessage(), e);
	       }
	    }
	}
	
	/**
	 * 添加装配件
	 * @param assembly {@link Assembly},装配件
	 * @param paths {@link Set}, 要监视目录集合
	 */
	public void addAssembly(Assembly assembly, Set<Path> paths) {
		if (!paths.isEmpty()) {
			int id = assembly.getId();
			paths.forEach(path ->{
				if (this.pathMap.putIfAbsent(path, id) == null) {
					if (this.watcher != null) {
						try {
							path.register(this.watcher, 
									StandardWatchEventKinds.ENTRY_CREATE, 
									StandardWatchEventKinds.ENTRY_DELETE,
							        StandardWatchEventKinds.ENTRY_MODIFY);
						} catch (IOException e) {
							if (logger.isErrorEnabled()) {
					        	logger.error(e.getMessage(), e);
					        }
						}
					}
				}
			});
		}
	}
	
	/**
	 * 文件发生改变事件
	 * @param event {@link WatchEvent}
	 */
	protected void onEvent(WatchKey key, WatchEvent<?> event) {
		 if (event.context() != null) {
			 if (event.context() instanceof Path) {
				 Path file = ((Path)key.watchable()).resolve((Path)(event.context()));
				 Path path = ((Path)key.watchable()).resolve((Path)(event.context())).getParent();
				 Integer id = this.pathMap.get(path);
				 if (id == null) {
					 return;
				 }
				 
				 if (Files.isDirectory(file)) {
					 //目录查找，则增加/移除新目录
					 if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
						 this.pathMap.computeIfAbsent(file, pathID ->{
							try {
								path.register(this.watcher, 
										StandardWatchEventKinds.ENTRY_CREATE, 
										StandardWatchEventKinds.ENTRY_DELETE,
								        StandardWatchEventKinds.ENTRY_MODIFY);
							} catch (IOException e) {
								if (logger.isErrorEnabled()) {
						        	logger.error(e.getMessage(), e);
						        }
							}
							 return id;
						 });
					 }else {
						 if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
							this.pathMap.remove(file);
						 }
					 }
				 }else {
					 if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
						 //删除模式下，可能无法判断删除的是目录。所以补充对目录的清除
						 this.pathMap.remove(file);
					 }
					 
					 //文件操作，则重启装配件
					 Assembly assembly = this.framework.findModuleById(id.intValue(), Assembly.class);
					 if (assembly != null) {
						 if (assembly.isWorking()) {
							 if (logger.isDebugEnabled()) {
								 logger.debug("restart " + assembly);
							 }
							 try {
								 this.framework.stop(assembly);
							 }catch(Exception e) {
								 if (logger.isErrorEnabled()) {
									 logger.error(e.getMessage(), e);
								 }
							 }
						 }else {
							 if (logger.isDebugEnabled()) {
								 logger.debug("starting " + assembly);
							 }
						 }
						 
						 try {
							 this.framework.start(assembly);
						 }catch(Exception e) {
							 if (logger.isErrorEnabled()) {
								 logger.error(e.getMessage(), e);
							 }
						 }
					 }
				 }
			 }
		 }
	}
	
	public void close() {
		try {
			this.watcher.close();
		} catch (IOException e) {
		}
	}

	@Override
	public void run() {		
		try {
			while (!Thread.currentThread().isInterrupted()){
				WatchKey key = null;
		        try {
		            //获取可用key.没有可用的就wait
		            key = this.watcher.take();
		        } catch (InterruptedException ex) {
		            continue;
		        }
		        
		        for (WatchEvent<?> event : key.pollEvents()) {
		        	try {
		        		this.onEvent(key, event);
		        	}catch(Throwable ex) {
		        		if (logger.isWarnEnabled()) {
		        			logger.warn(ex.getMessage(), ex);
		        		}
		        	}
		        }
		        
		        //重置，这一步很重要，否则当前的key就不再会获取将来发生的事件
		        boolean valid = key.reset();
		        //失效状态，退出监听
		        if (!valid) {
		            break;
		        }
			}
		}catch(Throwable e) {
			if (logger.isErrorEnabled()) {
				logger.error(e.getMessage(), e);
			}
		}
	}
}
